#ifndef CLIENT_HPP
#define CLIENT_HPP

#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstring>
#include "sys/socket.h"
#include "unistd.h"
#include "arpa/inet.h"
#include "signal.h"
#include "json.hpp"
#include "consistenthash.hpp"
#include "wrap.hpp"

#define KEY_VALUE_REQUEST 0
#define KEY_VALUE_RESPOND 1
#define HEART_BEAT 2
#define DISTRIBUTION_REQUEST 3
#define DISTRIBUTION_RESPOND 4
#define CACHE_SHUNTDOWN 5
#define CACHE_JOIN 6
#define MASTER_IP "127.0.0.1:7000"
#define CACHE_IP "127.0.0.1:8000"



using json = nlohmann::json;

struct OneIPList
{
public:
    std::string ip;
    int port;
};

class Client : public OneIPList
{
public:
    Client();
    Client(std::vector<std::string> iplist);
    ~Client();
    std::vector<std::string> alliplist;
    std::vector<std::string> keylist;
    void KeyValueWrite();
    void DistributionRequest();
    std::string KeyValueRequest(std::string key);
    
private:
    ConsistentHash hash;
    std::string Randstr(const int len);
    json KeyValuejs();
    json KeyValuejs(std::string key);
    OneIPList GetIPList(std::string cacheip);
    json ReadData(int &cfd);
    int ConnectServ(OneIPList oneip,int& cfd);
    int kvwritecfd,kvreqcfd,mastercfd;
    struct sockaddr_in serv_addr;
    socklen_t serv_addr_len;
};

Client::Client(){
    //无初始给定值时，先向master请求一次分布
    DistributionRequest();
    ConsistentHash hash(100);
}
Client::Client(std::vector<std::string> iplist){
    //本地初始化iplist分布
    alliplist.swap(iplist);
    ConsistentHash hash(100);
    hash.RefreshIPList(alliplist);
}
Client::~Client(){
}

std::string Client::Randstr(const int len)
{
    std::string ans;
    int i;
    for (i = 0; i < len; i++)
    {
        char c;
        switch ((rand() % 3))
        {
        case 1:
            c = 'A' + rand() % 26;
            break;
        case 2:
            c = 'a' + rand() % 26;
            break;
        default:
            c = '0' + rand() % 10;
        }
        ans += c;
    }
    ans[++i] = '\0';
    return ans;
}

json Client::KeyValuejs()
{
    json js, data;
    data["key"] = Client::Randstr(20);
    data["value"] = Client::Randstr(200);
    data["flag"] = true;
    js["type"] = KEY_VALUE_RESPOND;
    js["data"] = data;
    return js;
}
json Client::KeyValuejs(std::string key)
{
    json js, data;
    data["flag"] = true;
    data["key"] = key;
    data["value"] = "";
    js["type"] = KEY_VALUE_REQUEST;
    js["data"] = data;
    return js;
}

OneIPList Client::GetIPList(std::string cacheip){
    int cut = sizeof(cacheip);
    while(cacheip[cut]!=':'){
        cut--;
    }
    OneIPList iptest;
    iptest.ip = cacheip.substr(0,cut);
    iptest.port = std::stoi(cacheip.substr(cut+1,sizeof(cacheip)));
    return iptest;
}


int Client::ConnectServ(OneIPList oneip,int &cfd){
    //signal(SIGPIPE, SIG_IGN);
    cfd = Socket(AF_INET, SOCK_STREAM, 0);
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    inet_pton(AF_INET, (oneip.ip.c_str()), &serv_addr.sin_addr.s_addr);
    serv_addr.sin_port = htons(oneip.port);
    int n = Connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    std::cout << "connect once" << std::endl;
    return n;
}

json Client::ReadData(int &cfd){
    char bufin[BUFSIZ];
    ssize_t n;
    do{
        Sleep_ms(100);
        n = Read(cfd, bufin, BUFSIZ);
    }while(n<=0);
    //Write(STDOUT_FILENO, bufin, n);//test
    std::stringstream stream;
    stream << bufin;
    std::string bufinstr = stream.str();
    json js = json::parse(bufinstr);
    std::cout << "read json data: " << js << std::endl;
    return js;
}

void Client::KeyValueWrite()
{
    int n,nwrite;
    int t = 0;
    int j = 0;
    json js = KeyValuejs(); //随机生成
    std::cout << "send json data: " << js << std::endl;
    std::string key = js["data"]["value"].get<std::string>();
    auto oneipbf = GetIPList(hash.GetServerIndex(key));
    
    n = ConnectServ(oneipbf,kvwritecfd);
    while(n<0){
        do{
            close(kvwritecfd);
            kvwritecfd = Socket(AF_INET, SOCK_STREAM, 0);
            n = connect(kvwritecfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
            sleep(1);
            if(n>=0)
                break;
            j++;
        }while(j<=3);
        if(n < 0){
            DistributionRequest();
            oneipbf = GetIPList(hash.GetServerIndex(key));
            n = ConnectServ(oneipbf,kvwritecfd);
        }
    }
    std::string buf = js.dump();
    nwrite = Write(kvwritecfd, (char *)buf.data(), buf.length());
    std::cout << "write once" << std::endl;
    while(nwrite <= 0){
        int cflag;
        do{
            sleep(1);
            close(kvwritecfd);
            kvwritecfd = Socket(AF_INET, SOCK_STREAM, 0);
            cflag = connect(kvwritecfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
        }while(cflag<0);
        nwrite = Write(kvwritecfd, (char *)buf.data(), buf.length());
    }
    while (1)
    {
        js = KeyValuejs(); //随机生成
        std::string buf = js.dump();
        std::cout << "send json data: " << js << std::endl;
        std::string key = js["data"]["key"].get<std::string>();
        keylist.push_back(key);
        auto oneip = GetIPList(hash.GetServerIndex(key));
        if(oneip.ip != oneipbf.ip || oneip.port != oneipbf.port)
        {
            Close(kvwritecfd);     //关闭之前的连接
            n = ConnectServ(oneip,kvwritecfd);
            oneipbf = oneip;
        }
        
        do{
            sleep(1);
            n = write(kvwritecfd, (char *)buf.data(), buf.length());
            std::cout << "write once" << std::endl;
            if(n>0)
                break;
        }while(errno == EINTR);
        if(n <= 0){
            int cflag;
            do{
                sleep(1);
                close(kvwritecfd);
                kvwritecfd = Socket(AF_INET, SOCK_STREAM, 0);
                cflag = connect(kvwritecfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
            }while(cflag<0);
        }
        t++;
    }
    Close(kvwritecfd);
}

std::string Client::KeyValueRequest(const std::string key){
    json js = KeyValuejs(key);
    std::cout << "send json data: " << js << std::endl;
    OneIPList oneip = GetIPList(hash.GetServerIndex(key));
    int n = ConnectServ(oneip,kvreqcfd);
    while(n != 0){
        //DistributionRequest();
        oneip = GetIPList(hash.GetServerIndex(key));
        n = ConnectServ(oneip,kvreqcfd);
    }
    std::string buf = js.dump();
    Write(kvreqcfd, (char *)buf.data(), buf.length());
    json jsin = ReadData(kvreqcfd);
    while(jsin["data"]["flag"] == false){
        Sleep_ms(100);
        Write(kvreqcfd, (char *)buf.data(), buf.length());
        json jsin = ReadData(kvreqcfd);
    }
    if(jsin["type"] != KEY_VALUE_RESPOND){
        PerrExit("Distribution Request returns fault");
    }
    if(jsin["data"]["key"] != key){
        PerrExit("Distribution Request returns fault");
    }
    Close(kvreqcfd);
    std::string value = jsin["data"]["value"].get<std::string>();
    std::cout << "return value: " << value << std::endl;
    return value;
}

void Client::DistributionRequest()
{
    OneIPList masterip = GetIPList(MASTER_IP);
    int n = ConnectServ(masterip,mastercfd);
    if(n != 0){
        PerrExit("connection to master failed");
    }
    json client_to_master;
    client_to_master["type"] = DISTRIBUTION_REQUEST;
    client_to_master["data"] = "";
    std::cout << client_to_master << std::endl;
    std::string buf = client_to_master.dump();
    Write(mastercfd, (char *)buf.data(), buf.length());
    json jsin = ReadData(mastercfd); 
    if(jsin["type"] != DISTRIBUTION_RESPOND){
        PerrExit("Distribution Request returns fault");
    }
    alliplist = jsin["data"]["iplist"].get<std::vector<std::string>>();
    hash.RefreshIPList(alliplist);
    Close(mastercfd);
}

#endif /* CLIENT_HPP */